#!/usr/bin/env python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Script that helps discover and run the various runnable services and
scripts in Socorro.

This command should be run in the Docker-based local development environment in
one of the Docker containers.

Run "socorro-cmd --help" for help.

"""

import argparse
import importlib
import inspect
import os
import sys
import textwrap
import types

import six

from socorro.app.socorro_app import App


def wrapped(text, width=80):
    """Wraps a block of text"""
    return '\n'.join(textwrap.wrap(text, width=width))


def showcommands_cmd(argv):
    """Sub command to show location of all command runners"""
    parser = argparse.ArgumentParser(
        description='Shows socorro-cmd subcommands and runner paths.'
    )
    parser.parse_args(argv)

    print('Available commands and runners for socorro-cmd:')
    print('')

    for group in COMMANDS:
        print('%s:' % group.name)
        for cmd, runner in group:
            if not isinstance(runner, six.string_types):
                runner = '%s in %s' % (runner.__name__, inspect.getfile(runner))
            print('  %-24s %s' % (cmd, runner))
        print('')


def django_cmd(cmd):
    """Run a Django command."""
    def _django_cmd(argv):
        argv = ['socorro-cmd', cmd] + argv
        sys.path.insert(0, 'webapp-django')
        import manage
        manage.main(argv)
    return _django_cmd


def import_path(python_path):
    """Import a name from a module."""
    module_path, name = python_path.rsplit('.', 1)
    module = importlib.import_module(module_path)
    return getattr(module, name)


class Group:
    """Defines a grouping of commands"""
    def __init__(self, name, cmd_to_run_map):
        self.name = name
        self.cmd_to_run_map = cmd_to_run_map

    def __iter__(self):
        return iter(sorted(self.cmd_to_run_map.items()))

    def lookup(self, cmd):
        return self.cmd_to_run_map.get(cmd)


COMMANDS = [
    Group(
        'Crash processing utilities', {
            'fetch_crashids': import_path('socorro.scripts.fetch_crashids.main'),
            'fetch_crash_data': import_path('socorro.scripts.fetch_crash_data.main'),
            'reprocess': import_path('socorro.scripts.reprocess.main'),
        }
    ),
    Group(
        'Local development environment maintenance', {
            'upload_telemetry_schema': import_path(
                'socorro.external.boto.upload_telemetry_schema.UploadTelemetrySchema'
            ),
            'db': import_path('socorro.scripts.db.main'),
            'es': import_path('socorro.scripts.es.main'),
            'pubsub': import_path('socorro.scripts.pubsub.main'),
            'depcheck': django_cmd('depcheck'),
        }
    ),
    Group(
        'Services', {
            'crontabber': import_path('socorro.cron.crontabber_app.CronTabberApp'),
            'processor': import_path('socorro.processor.processor_app.ProcessorApp'),
        }
    ),
    Group(
        'Miscellaneous', {
            'showcommands': showcommands_cmd,
            'signature': import_path('socorro.signature.cmd_signature.main'),
            'signature-doc': import_path('socorro.signature.cmd_doc.main'),
        }
    )
]


def import_and_run(app):
    """Takes a runner and runs it

    Runners can be either a function or a Socorro App.

    """
    # If the app is a main function, we run it as is
    if isinstance(app, types.FunctionType):
        sys.exit(app(sys.argv[1:]))

    # If the app is a Socorro App, we run it using the Socorro App main
    if issubclass(app, App):
        sys.exit(app.run())

    print('ProgrammerError: Unknown runner type')
    sys.exit(1)


def build_epilog():
    """Builds the epilog containing the groups and commands"""
    output = []
    output.append('Available commands:')
    output.append('\n')
    for group in COMMANDS:
        output.append('%s:' % group.name)
        for cmd, runner in group:
            output.append('  %s' % cmd)
        output.append('\n')

    output.append('')
    output.append('Type "socorro-cmd CMD --help" for help on any command.')
    output.append('')
    output.append(
        wrapped(
            'All commands need to be run in a Docker container. Service commands are run in '
            'the related container. For example, the "processor" command should run in the '
            '"processor" container. All other commands should be run in the processor '
            'container.'
        )
    )
    output.append('')
    output.append(
        wrapped(
            'For more documentation, see <https://socorro.readthedocs.io/>.'
        )
    )
    return '\n'.join(output)


def get_runner(cmd):
    """Given a cmd, returns the runner"""
    for group in COMMANDS:
        runner = group.lookup(cmd)
        if runner is not None:
            return runner


def cmd_main():
    # Build a basic parser so we can take advantage of --help
    parser = argparse.ArgumentParser(
        prog='socorro-cmd',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=build_epilog()
    )
    parser.add_argument(
        'cmd',
        nargs='?',
        help='command to run'
    )
    parser.add_argument(
        'arg',
        nargs='*',
        help='command arguments'
    )

    if not sys.argv[1:] or sys.argv[1] in ('-h', '--help'):
        parser.print_help()
        return 0

    cmd = sys.argv[1]
    runner = get_runner(cmd)
    if runner is None:
        parser.error('"%s" is not a valid command; see --help for command list' % cmd)

    # Rewrite sys.argv so anything that parses sys.argv has the right one
    sys.argv = [sys.argv[0] + ' ' + cmd] + sys.argv[2:]
    import_and_run(runner)


if __name__ == '__main__':
    cmd_main()
